package cn.walls1717.wallsmall.ware.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.walls1717.wallsmall.common.pojo.to.OrderTO;
import cn.walls1717.wallsmall.common.pojo.to.mq.StockDetailTO;
import cn.walls1717.wallsmall.common.pojo.to.mq.StockLockedTO;
import cn.walls1717.wallsmall.common.utils.PageUtils;
import cn.walls1717.wallsmall.common.utils.Query;
import cn.walls1717.wallsmall.common.utils.R;
import cn.walls1717.wallsmall.ware.dao.WareSkuDao;
import cn.walls1717.wallsmall.ware.entity.PurchaseDetailEntity;
import cn.walls1717.wallsmall.ware.entity.WareOrderTaskDetailEntity;
import cn.walls1717.wallsmall.ware.entity.WareOrderTaskEntity;
import cn.walls1717.wallsmall.ware.entity.WareSkuEntity;
import cn.walls1717.wallsmall.ware.exception.NoStockException;
import cn.walls1717.wallsmall.ware.feign.OrderFeignService;
import cn.walls1717.wallsmall.ware.feign.ProductFeignService;
import cn.walls1717.wallsmall.ware.pojo.bo.OrderBO;
import cn.walls1717.wallsmall.ware.pojo.bo.SkuWareHasStockBO;
import cn.walls1717.wallsmall.ware.pojo.vo.OrderItemVO;
import cn.walls1717.wallsmall.ware.pojo.vo.SkuHasStockVO;
import cn.walls1717.wallsmall.ware.pojo.vo.WareSkuLockVO;
import cn.walls1717.wallsmall.ware.service.WareOrderTaskDetailService;
import cn.walls1717.wallsmall.ware.service.WareOrderTaskService;
import cn.walls1717.wallsmall.ware.service.WareSkuService;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static cn.walls1717.wallsmall.common.constant.order.OrderStatusEnum.CANCELLED;
import static cn.walls1717.wallsmall.common.constant.ware.WareOrderTaskDetailStatusEnum.LOCKED;
import static cn.walls1717.wallsmall.common.constant.ware.WareOrderTaskDetailStatusEnum.UNLOCKED;

@Service("wareSkuService")
@Slf4j
public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {

    @Resource
    private ProductFeignService productFeignService;
    @Resource
    private RabbitTemplate rabbitTemplate;
    @Resource
    private WareOrderTaskService wareOrderTaskService;
    @Resource
    private WareOrderTaskDetailService wareOrderTaskDetailService;
    @Resource
    private OrderFeignService orderFeignService;

    /**
     * 分页条件查询
     *
     * @param params 查询条件
     * @return 分页数据
     */
    @Override
    public PageUtils listPagesByConditions(Map<String, Object> params) {
        LambdaQueryWrapper<WareSkuEntity> lqw = new LambdaQueryWrapper<>();
        // 查询条件wareId
        String wareId = (String)params.get("wareId");
        if (!StrUtil.isEmpty(wareId) && !StrUtil.equals(wareId, "0")) {
            lqw.eq(WareSkuEntity::getWareId, wareId);
        }
        // 查询条件skuId
        String skuId = (String)params.get("skuId");
        if (!StrUtil.isEmpty(skuId) && !StrUtil.equals(skuId, "0")) {
            lqw.eq(WareSkuEntity::getSkuId, skuId);
        }
        IPage<WareSkuEntity> page = this.page(new Query<WareSkuEntity>().getPage(params), lqw);

        return new PageUtils(page);
    }

    /**
     * 保存库存，如果库存已存在则增加
     *
     * @param detail 采购信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveStock(PurchaseDetailEntity detail) {
        Long skuId = detail.getSkuId();
        Long wareId = detail.getWareId();
        Integer skuNum = detail.getSkuNum();
        // 根据skuId和wareId获取sku库存信息
        List<WareSkuEntity> wareSkus =
            lambdaQuery().eq(WareSkuEntity::getSkuId, skuId).eq(WareSkuEntity::getWareId, wareId).list();
        // 能获取到库存信息则直接增加库存数量
        if (wareSkus != null && wareSkus.size() != 0) {
            baseMapper.updateStockBySkuIdAndWareId(skuId, wareId, skuNum);
            return;
        }

        // 不能获取到库存信息就新增
        WareSkuEntity wareSku = new WareSkuEntity();
        wareSku.setSkuId(skuId);
        wareSku.setWareId(wareId);
        wareSku.setStock(skuNum);
        wareSku.setStockLocked(0);
        String skuName = productFeignService.getSkuNameBySkuId(skuId);
        wareSku.setSkuName(skuName);
        save(wareSku);
    }

    /**
     * 根据skuId查询sku是否有库存
     *
     * @param skuIds skuId
     * @return sku库存状态
     */
    @Override
    public List<SkuHasStockVO> getHasStock(List<Long> skuIds) {
        return skuIds.stream().map(skuId -> {
            SkuHasStockVO skuHasStockVO = new SkuHasStockVO();
            // 可能获取不到库存，则会出现null，因此需要包装类封装
            Long count = baseMapper.getSkuStockBySkuId(skuId);
            skuHasStockVO.setSkuId(skuId);
            // count为null也表示没有库存，设置为false，库存数量大于0，表示有库存，设置为true
            skuHasStockVO.setHasStock(count != null && count > 0);
            return skuHasStockVO;
        }).collect(Collectors.toList());
    }

    /**
     * 锁定库存
     *
     * @param stockLock 被锁的sku数据
     * @return 锁定结果
     */
    @Override
    @Transactional(rollbackFor = NoStockException.class)
    public Boolean orderStockLock(WareSkuLockVO stockLock) {
        // 保存库存工作单
        WareOrderTaskEntity wareOrderTask = new WareOrderTaskEntity();
        wareOrderTask.setOrderSn(stockLock.getOrderSn());
        wareOrderTaskService.save(wareOrderTask);

        // 获取当前sku在哪个仓库有库存可以进行锁定
        List<OrderItemVO> locks = stockLock.getLocks();
        List<SkuWareHasStockBO> wareHasStock = locks.stream().map(item -> {
            SkuWareHasStockBO skuWareHasStockBO = new SkuWareHasStockBO();
            Long skuId = item.getSkuId();
            skuWareHasStockBO.setSkuId(skuId);
            // 根据当前skuId查到有库存的仓库id集合
            List<Long> wareIds = baseMapper.listWareIdsHasSkuStockBySkuId(skuId);
            // 设置有库存的仓库id集合
            skuWareHasStockBO.setWareIds(wareIds);
            // 设置需要锁定的库存数量
            skuWareHasStockBO.setNum(item.getCount());
            return skuWareHasStockBO;
        }).collect(Collectors.toList());

        for (SkuWareHasStockBO hasStock : wareHasStock) {
            // 是否锁定成功标志[true成功，false失败]
            boolean skuLockFlag = false;
            Long skuId = hasStock.getSkuId();
            List<Long> wareIds = hasStock.getWareIds();
            // 没有库存id，则直接返回没有库存异常。
            if (wareIds == null || wareIds.size() == 0) {
                throw new NoStockException(skuId);
            }

            for (Long wareId : wareIds) {
                // 根据skuId和wareId到数据库中锁定库存
                Long result = baseMapper.updateStockLockedBySkuIdAndWareId(wareId, skuId, hasStock.getNum());
                // 结果为1，说明库存锁定成功，生成库存工作单详情。
                if (result == 1) {
                    // 设置锁定库存标志为成功
                    skuLockFlag = true;
                    WareOrderTaskDetailEntity wareOrderTaskDetail = new WareOrderTaskDetailEntity();
                    wareOrderTaskDetail.setSkuId(skuId);
                    // 锁定的库存数量
                    wareOrderTaskDetail.setSkuNum(hasStock.getNum());
                    wareOrderTaskDetail.setTaskId(wareOrderTask.getId());
                    wareOrderTaskDetail.setWareId(wareId);
                    // 设置锁定状态[1已锁定，2已解锁，3扣减]
                    wareOrderTaskDetail.setLockStatus(LOCKED.getCode());
                    // 保存库存工作单详情
                    wareOrderTaskDetailService.save(wareOrderTaskDetail);

                    // 设置工作单id与工作单详情的关联关系
                    StockLockedTO stockLockedTO = new StockLockedTO();
                    stockLockedTO.setId(wareOrderTask.getId());
                    StockDetailTO stockDetailTO = new StockDetailTO();
                    BeanUtil.copyProperties(wareOrderTaskDetail, stockDetailTO);
                    stockLockedTO.setDetail(stockDetailTO);

                    // 向消息队列中延迟队列发送库存已锁定的消息，等待超时自动解锁。
                    rabbitTemplate.convertAndSend("stock-event-exchange", "stock.locked", stockLockedTO);

                    // 因为可能有多个仓库中有库存，所以只要有一个仓库锁定库存成功，直接跳出循环，不再锁定。
                    break;
                }
            }
            // 若订单中有一个商品没有锁定成功，则返回没有库存异常。
            if (!skuLockFlag) {
                throw new NoStockException(skuId);
            }
        }
        return true;
    }

    /**
     * 库存解锁
     *
     * @param stockLockedTO 库存解锁数据
     */
    @Override
    public void unLockStock(StockLockedTO stockLockedTO) {
        StockDetailTO detail = stockLockedTO.getDetail();
        Long detailId = detail.getId();
        WareOrderTaskDetailEntity orderTaskDetail = wareOrderTaskDetailService.getById(detailId);
        /*
         * 解锁逻辑：
         * 查询数据库关于这个订单的锁定库存信息：
         *   能查到：
         *     查询订单情况：
         *       没有这个订单 -> 必须解锁库存
         *       有这个订单：
         *         订单状态已取消 -> 解锁库存
         *         订单状态没有取消 -> 不能解锁库存
         *   不能查到：
         *     证明库存锁定失败，数据库回滚，无需解锁
         */
        if (orderTaskDetail == null) {
            // 没有库存工作单详情，无需解锁库存，直接结束方法，不抛出异常。
            return;
        }

        WareOrderTaskEntity orderTask = wareOrderTaskService.getById(stockLockedTO.getId());
        String orderSn = orderTask.getOrderSn();
        // 查询订单状态，若订单状态远程查询失败，直接抛出异常。
        R r = orderFeignService.getOrderStatus(orderSn);
        if (r.getCode() != 0) {
            throw new RuntimeException("远程服务失败");
        }
        OrderBO orderBO = r.getData("data", new TypeReference<OrderBO>() {});
        // 订单不存在，无需解锁库存，方法直接返回确认消息。
        if (orderBO == null) {
            return;
        }

        // 订单状态为已取消，库存工作单详情为已锁定，执行解锁库存逻辑。
        if (Objects.equals(orderBO.getStatus(), CANCELLED.getCode()) && Objects.equals(orderTaskDetail.getLockStatus(), LOCKED.getCode())) {
            unLockStockBySkuIdAndWareId(detail.getSkuId(), detail.getWareId(), detail.getSkuNum(), detailId);
        }
    }

    /**
     * 解锁库存，订单直接发送到解锁队列。<br>
     * 防止订单服务卡顿，订单状态一致没有改变，库存消息优先到期，查订单状态依然是新建状态，导致库存一直无法解锁。
     *
     * @param order 解锁数据
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unLockStock(OrderTO order) {
        WareOrderTaskEntity wareOrderTask = wareOrderTaskService.getByOrderSn(order.getOrderSn());
        // 获取库存工作单详情
        List<WareOrderTaskDetailEntity> wareOrderTaskDetails =
            wareOrderTaskDetailService.listLockedByTaskId(wareOrderTask.getId());

        // 根据库存工作单解锁库存
        for (WareOrderTaskDetailEntity wareOrderTaskDetail : wareOrderTaskDetails) {
            unLockStockBySkuIdAndWareId(wareOrderTaskDetail.getSkuId(), wareOrderTaskDetail.getWareId(),
                wareOrderTaskDetail.getSkuNum(), wareOrderTaskDetail.getId());
        }
    }

    /**
     * 解锁库存
     *
     * @param skuId skuId
     * @param wareId wareId
     * @param num 数量
     * @param taskDetailId wareOrderTaskDetailId
     */
    @Transactional(rollbackFor = Exception.class)
    public void unLockStockBySkuIdAndWareId(Long skuId, Long wareId, Integer num, Long taskDetailId) {
        // 恢复已经锁定的库存
        baseMapper.unLockStockBySkuIdAndWareId(skuId, wareId, num);

        WareOrderTaskDetailEntity wareOrderTaskDetail = new WareOrderTaskDetailEntity();
        wareOrderTaskDetail.setId(taskDetailId);
        // 设置库存工作单详情为已解锁状态
        wareOrderTaskDetail.setLockStatus(UNLOCKED.getCode());
        wareOrderTaskDetailService.updateById(wareOrderTaskDetail);
    }
}